/*
 * bsplua.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version bsplua.c 1.0.0
 * @package libbsplua
 * @author Dr.NP <np@bsgroup.org>
 * @update 04/27/2011
 */

/**
 * Embed script support for lua bind
 *
 * === CHANGELOG ===
 * [04/27/2011] - Creation
 */

#include "bsp.h"

#include "bsp_script.h"
#include "bsp_utils.h"

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <sys/time.h>
#include <sys/types.h>
#include <pthread.h>

pthread_mutex_t script_list_lock = PTHREAD_MUTEX_INITIALIZER;

void lua_clear_stack(lua_State *);
void lua_dump_stack(lua_State *);

struct script_t **script_list;
size_t script_list_size;
int script_total;
struct script_t **free_script_list;
size_t free_script_list_size;
int free_script_total;

int nthreads = 0;

int script_init(int nworkers)
{
    script_list_size = 
        script_total = 
        free_script_list_size = 
        free_script_total = 0;
    
    script_list = ensure_list_space(NULL, sizeof(struct script_t *), &script_list_size, SCRIPT_LIST_INITIAL);
    free_script_list = ensure_list_space(NULL, sizeof(struct script_t *), &free_script_list_size, SCRIPT_LIST_INITIAL);
    
    if (!script_list || !free_script_list)
    {
        err(RTN_ERROR_MEMORY_ALLOC, "Script list error!!!");
    }

    if (nworkers > 0)
    {
        nthreads = nworkers;
    }
    
    return RTN_SUCCESS;
}

int new_script()
{
    struct script_t *curr = NULL;
    
    // Find a free script container
    if (free_script_total > 0)
    {
        curr = free_script_list[-- free_script_total];
        script_list[curr->id] = curr;
    }

    else
    {
        curr = malloc(sizeof(struct script_t));

        if (!curr)
        {
            return RTN_ERROR_MEMORY_ALLOC;
        }

        memset(curr, 0, sizeof(struct script_t));

        while (script_total >= script_list_size)
        {
            script_list = ensure_list_space(script_list, sizeof(struct script_t *), &script_list_size, script_list_size * 2);
            if (!script_list)
            {
                err(RTN_ERROR_MEMORY_ALLOC, "Script list error!!!");
            }
        }

        script_list[script_total] = curr;
        curr->id = script_total;
    }

    curr->load_times = 0;
    curr->in_use = 1;
    curr->is_loaded = 0;
    script_total ++;

    return curr->id;
}

int del_script(struct script_t *s)
{
    int i;
    
    if (!s)
    {
        return RTN_ERROR_GENERAL;
    }

    int id = s->id;
    if (s->worker_states)
    {
        for (i = 0; i < nthreads; i ++)
        {
            if (s->worker_states[i])
            {
                lua_close(s->worker_states[i]);
            }
        }

        s->nwstates = nthreads;
    }

    if (s->main_state)
    {
        lua_close(s->main_state);
    }

    memset(s, 0, sizeof(struct script_t));

    s->id = id;

    while (free_script_total >= free_script_list_size)
    {
        free_script_list = ensure_list_space(free_script_list, sizeof(struct script_t *), &free_script_list_size, free_script_list_size * 2);

        if (free_script_list)
        {
            err(RTN_ERROR_MEMORY_ALLOC, "Free script list error!!!");
        }
    }

    free_script_list[free_script_total ++] = s;
    script_total --;
    script_list[id] = NULL;
    
    return RTN_SUCCESS;
}

struct script_t * get_script(int sid)
{
    if (sid < 0 || sid >= script_list_size)
    {
        return NULL;
    }

    return script_list[sid];
}

int load_script(struct script_t *s, const char *filename)
{
    int res, i;
    
    if (!s || !filename || 0 == strlen(filename))
    {
        return RTN_ERROR_GENERAL;
    }

    if (s->is_loaded)
    {
        unload_script(s);
    }

    if (0 == strlen(s->script_filename) || (0 != strcmp(filename, s->script_filename)))
    {
        // A different script
        s->load_times = 0;
    }

    snprintf(s->script_filename, MAX_SCRIPT_FILENAME_LENGTH - 1, "%s", filename);

    if (s->main_state)
    {
        lua_close(s->main_state);
    }

    s->main_state = luaL_newstate();
    if (!s->main_state)
    {
        return RTN_ERROR_SCRIPT;
    }

    luaL_openlibs(s->main_state);
    res = luaL_loadfile(s->main_state, (const char *) s->script_filename);
    if (0 != res || 0 != lua_pcall(s->main_state, 0, 0, 0))
    {
        debug_info("Lua state : Load script <%s> error -> %s\n", s->script_filename, lua_tostring(s->main_state, -1));

        return RTN_ERROR_SCRIPT_LOAD;
    }

    s->is_loaded = 0x1;
    if (0 == s->is_conf)
    {
        // Fork nthreads
        if (s->worker_states)
        {
            for (i = 1; i <= nthreads; i ++)
            {
                if (s->worker_states[i]);
                {
                    lua_close(s->worker_states[i]);
                }
            }
        }

        else
        {
            s->worker_states = malloc(sizeof(struct lua_State *) * (nthreads + 1));
        }

        for (i = 1; i <= nthreads; i ++)
        {
            s->worker_states[i] = lua_newthread(s->main_state);

            // Add state to registry for LUA GC
            lua_pushvalue(s->main_state, -1);
            luaL_ref(s->main_state, LUA_REGISTRYINDEX);

            luaL_openlibs(s->worker_states[i]);
        }
/*
        if (0 == s->load_times)
        {
            lua_getglobal(s->main_state, SCRIPT_CB_ON_LOAD);
            if (lua_pcall(s->main_state, 0, 0, 0))
            {
                debug_info("LUA state : Script <%s> has no available function %s\n", s->script_filename, SCRIPT_CB_ON_LOAD);
            }
        }

        else
        {
            lua_getglobal(s->main_state, SCRIPT_CB_ON_RELOAD);
            if (lua_pcall(s->main_state, 0, 0, 0))
            {
                debug_info("LUA state : Script <%s> has no available function %s\n", s->script_filename, SCRIPT_CB_ON_RELOAD);
            }
        }
*/
        s->nwstates = nthreads;
    }

    s->load_times ++;

    s->load_time = time((time_t *) NULL);
    debug_info("Lua state : Script <%s> loaded\n", s->script_filename);

    return RTN_SUCCESS;
}

int unload_script(struct script_t *s)
{
    int i;

    if (!s || !s->main_state)
    {
        return RTN_ERROR_SCRIPT;
    }

    if (!s->is_loaded)
    {
        return RTN_SUCCESS;
    }
/*
    lua_getglobal(s->main_state, SCRIPT_CB_ON_UNLOAD);
    if (lua_pcall(s->main_state, 0, 0, 0))
    {
        debug_info("Lua state : Script <%s> has no available function %s\n", s->script_filename, SCRIPT_CB_ON_UNLOAD);
    }
*/
    if (0 == s->is_conf && s->worker_states)
    {
        for (i = 0; i < nthreads; i ++)
        {
            lua_close(s->worker_states[i]);
        }
    }

    lua_close(s->main_state);
    s->is_loaded = 0x0;

    return RTN_SUCCESS;
}
/*
int script_reg_funcs(struct script_t *s, const luaL_Reg *f)
{
    int i;

    if (!s || !s->main_state || !f)
    {
        return RTN_ERROR_SCRIPT;
    }

    if (!s->is_loaded)
    {
        return RTN_SUCCESS;
    }

    if (0 == s->is_conf && s->worker_states)
    {
        for (i = 0; i < nthreads; i ++)
        {
            debug_info("Reg funcs at thread %d\n", i);
            luaL_register(s->worker_states[i], NULL, f);
        }
    }
    
    return RTN_SUCCESS;
}
*/

void clear_stack(lua_State *l)
{
    if (l)
    {
        lua_settop(l, 0);
    }

    return;
}

void dump_stack(lua_State *l)
{
    int i, top, t;
    
    if (l)
    {
        top = lua_gettop(l);
        if (top < 1)
        {
            printf("Stack is empty.\n");

            return;
        }

        printf("=== %d ITEM(s) IN STACK ===\n\n", top);

        for (i = 1; i <= top; i ++)
        {
            t = lua_type(l, i);
            switch (t)
            {
                case LUA_TSTRING :
                    printf("\tString : '%s'\n", lua_tostring(l, i));
                    break;

                case LUA_TBOOLEAN :
                    printf(lua_toboolean(l, i) ? "\tBoolean : true\n" : "\tBoolean : false\n");
                    break;

                case LUA_TNUMBER :
                    printf("\tNumber : %g\n", lua_tonumber(l, i));
                    break;

                case LUA_TNIL :
                    printf("\tNil : nil\n");
                    break;

                case LUA_TTABLE :
                    printf("\tTable : table\n");
                    break;

                case LUA_TTHREAD :
                    printf("\tThread : thread\n");
                    break;

                case LUA_TUSERDATA :
                    printf("\tUserData : userdata\n");
                    break;

                case LUA_TFUNCTION :
                    printf("\tFunction : function\n");
                    break;

                default :
                    printf("\t*Unknown : %s\n", lua_typename(l, t));
                    break;
            }
        }

        printf("\n=== END OF STACK ===\n\n");
    }
}
